package edu.hm.compiler;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Scanner;
import java.util.StringTokenizer;

import edu.hm.compiler.interpreter.Interpreter;
import edu.hm.compiler.parser.derive.ASTBuilder;
import edu.hm.compiler.parser.derive.ParserException;
import edu.hm.compiler.parser.grammar.RuleSystem;
import edu.hm.compiler.scanner.Driver;
import edu.hm.compiler.scanner.dfa.MLOGDfa;
import edu.hm.compiler.scanner.token.IToken;

/**
 * @author Korny
 */
public class Main
{
    private static final String LOGO     = "/edu/hm/compiler/logo.txt";
    private static final String ENCODING = "utf-8";
    private static final String OUTRO    = "/edu/hm/compiler/outro.txt";
    private static final String EXIT     = "#";
    private static final String LAUNCH   = "&";
    private static final int    _58      = 58;

    /**
     * @param args
     * @throws IOException
     * @throws FileNotFoundException
     * @throws ParserException
     */
    public static void main( final String[] args ) throws FileNotFoundException, IOException, ParserException
    {
        Main.printlogo();
        try( final InputStream mlogDfa = Main.class.getResourceAsStream( "/edu/hm/compiler/scanner/mlog.dfa" ); ) //$NON-NLS-1$
        {
            final MLOGDfa dfa = new MLOGDfa( new InputStreamReader( mlogDfa ) );
            InputStream inputMlog = null;
            if( Main.checkArgs( args ) )
                try
                {
                    inputMlog = new FileInputStream( args[0] );
                    final Driver scanner = new Driver( dfa, inputMlog );
                    final ASTBuilder astBuilder = new ASTBuilder( scanner );
                    final RuleSystem ast = astBuilder.getAST();
                    final Interpreter interpreter = new Interpreter( ast );
                    interpreter.run();
                    System.out.println( interpreter.isResolved() );
                } catch( final ParserException e )
                {
                    final IToken errortoken = e.getToken();
                    System.err.println( I18NCompiler.getString( "ParsingError",//$NON-NLS-1$
                            errortoken.getLine(),
                            errortoken.getCharPos(),
                            errortoken.getPattern(),
                            errortoken.getTokenClass() ) );
                } catch( final Exception e )
                {
                    e.printStackTrace();
                } finally
                {
                    if( inputMlog != null )
                        try
                        {
                            inputMlog.close();
                        } catch( final Exception e )
                        {/* Egal */}
                }
            else
            {
                final Scanner scanner = new Scanner( System.in );
                StringBuilder builder = new StringBuilder();
                while( scanner.hasNextLine() )
                {
                    final String next = scanner.nextLine();
                    if( next.equals( Main.LAUNCH ) )
                    {
                        System.out
                                .println( "+-------------------" //$NON-NLS-1$
                                        + Main.fillUp(
                                                I18NCompiler.getString( I18NCompiler.getString( "Interprete" ) ), 20 ) + "-------------------+" ); //$NON-NLS-1$ //$NON-NLS-2$
                        final String input = builder.toString();
                        builder = new StringBuilder();
                        try
                        {
                            inputMlog = new ByteArrayInputStream( input.getBytes() );
                            final Driver driver = new Driver( dfa, inputMlog );
                            final ASTBuilder astBuilder = new ASTBuilder( driver );
                            final RuleSystem ast = astBuilder.getAST();
                            final Interpreter interpreter = new Interpreter( ast );
                            interpreter.run();
                            System.out.println( interpreter.isResolved() );
                        } catch( final ParserException e )
                        {
                            final IToken errortoken = e.getToken();
                            System.err.println( I18NCompiler.getString( "ParsingError",//$NON-NLS-1$
                                    errortoken.getLine(),
                                    errortoken.getCharPos(),
                                    errortoken.getPattern(),
                                    errortoken.getTokenClass() ) );
                        } finally
                        {
                            if( inputMlog != null )
                                try
                                {
                                    inputMlog.close();
                                } catch( final Exception e )
                                {/* ignored */}
                        }
                        System.out.println( "+----------------------------------------------------------+" ); //$NON-NLS-1$
                    } else if( next.equals( Main.EXIT ) )
                        break;
                    else
                        builder.append( next );
                }
                scanner.close();
            }
        } catch( final Exception e )
        {
            e.printStackTrace();
        }
        Main.printOutro();
    }

    private static void printOutro()
    {

        try( final BufferedReader br = new BufferedReader( new InputStreamReader(
                Main.class.getResourceAsStream( Main.OUTRO ), Main.ENCODING ) ); )
        {
            System.out.println();
            System.out.println( "+----------------------------------------------------------+" ); //$NON-NLS-1$
            System.out
                    .println( "|" + Main.fillUp( I18NCompiler.getString( I18NCompiler.getString( "Bye" ) ), Main._58 ) + "|" ); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
            System.out
                    .println( "|" + Main.fillUp( I18NCompiler.getString( I18NCompiler.getString( "CreatedBy" ) ), Main._58 ) + "|" ); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
            System.out.println( "|" + Main.fillUp( "Philippe Brousse", Main._58 ) + "|" ); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
            System.out.println( "|" + Main.fillUp( "Philipp Hauck Thalheim", Main._58 ) + "|" ); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
            System.out.println( "+----------------------------------------------------------+" ); //$NON-NLS-1$
            while( br.ready() )
            {
                System.out.println( br.readLine() );
                Thread.sleep( 100 );
            }
        } catch( final IOException | InterruptedException e )
        {
            /* Wumpe */
        }
    }

    private static void printlogo()
    {
        try( final BufferedReader br = new BufferedReader( new InputStreamReader(
                Main.class.getResourceAsStream( Main.LOGO ), Main.ENCODING ) ); )
        {
            while( br.ready() )
            {
                System.out.println( br.readLine() );
                Thread.sleep( 100 );
            }
            final Date date = new Date();
            final String formattedDate = DateFormat.getDateInstance( DateFormat.LONG, Locale.getDefault() )
                    .format( date );
            final String formattedTime = DateFormat.getTimeInstance( DateFormat.LONG, Locale.getDefault() )
                    .format( date );
            System.out.println();
            System.out.println( "+----------------------------------------------------------+" ); //$NON-NLS-1$
            System.out.println( "|                   MLOG Interpreter 0.99                  |" ); //$NON-NLS-1$
            System.out.println( "|                                                          |" ); //$NON-NLS-1$
            System.out.println( "|" + Main.fillUp( formattedDate, Main._58 ) + "|" ); //$NON-NLS-1$ //$NON-NLS-2$
            System.out.println( "|" + Main.fillUp( formattedTime, Main._58 ) + "|" ); //$NON-NLS-1$ //$NON-NLS-2$
            System.out.println( "+----------------------------------------------------------+" ); //$NON-NLS-1$
        } catch( final IOException | InterruptedException e )
        {}
    }

    private static String fillUp( final String format, final int to )
    {
        String filled = format;
        final int oldLen = format.length();
        final int target = to;
        final int diff = target - oldLen;
        for( int i = 0; i < diff / 2; i++ )
            filled = " " + filled + " "; //$NON-NLS-1$ //$NON-NLS-2$
        if( diff % 2 == 1 )
            filled += " "; //$NON-NLS-1$
        return filled;
    }

    private static boolean checkArgs( final String[] args )
    {
        if( args == null || args.length < 1 )
        {
            final List< String > text = new ArrayList<>();
            text.addAll( Main.breakUpTo( I18NCompiler.getString( "InteractiveMode" ), Main._58 ) );//$NON-NLS-1$
            text.addAll( Main.breakUpTo( I18NCompiler.getString( "Input" ), Main._58 ) );//$NON-NLS-1$
            text.addAll( Main.breakUpTo( I18NCompiler.getString( "Interpreter" ), Main._58 ) );//$NON-NLS-1$
            text.addAll( Main.breakUpTo( I18NCompiler.getString( "Terminate" ), Main._58 ) );//$NON-NLS-1$

            for( final String line: text )
                System.out.println( "|" + Main.fillUp( line, Main._58 ) + "|" ); //$NON-NLS-1$ //$NON-NLS-2$
            System.out.println( "+----------------------------------------------------------+" ); //$NON-NLS-1$

            return false;
        }
        return true;
    }

    private static List< String > breakUpTo( final String toBreak, final int lineLength )
    {
        final List< String > parts = new ArrayList<>();
        final StringTokenizer tk = new StringTokenizer( toBreak );
        StringBuilder builder = new StringBuilder();
        while( tk.hasMoreTokens() )
        {
            final String token = tk.nextToken();
            if( builder.length() + token.length() <= lineLength )
                builder.append( token ).append( ' ' );
            else
            {
                parts.add( builder.toString() );
                builder = new StringBuilder( token ).append( ' ' );
            }
        }
        if( builder.length() > 0 )
            parts.add( builder.toString() );
        return parts;
    }
}
